from cStringIO import StringIO
from Products.KnowledgeEngine.KERL.KERLObject import KERLObject

from Products.KnowledgeEngine.KERL.KERLExceptions import *


class Operator(KERLObject):
    """
        A Base class for all operators
        
        An operator is used to determine the action to compare with
        i.e. equals, contain, createdbefore
    """
        
    _implements = KERLObject._implements + ("Operator",)
     
    __allow_access_to_unprotected_subobjects__ = 1
     
    _opname = "(Unknown)"
     
    def __init__(self, a, b):  # Override in subclass to choose different number of arguments
        """ 
            (Almost) All Operators take two arguments- a and b           
        """
        
        self.a = a 
        self.b = b    
    
    # ------
    
    @classmethod
    def OperatorName(cls):
        """ Returns this operators name """
        
        return cls._opname
       
    def getFirstToken( self ):
        return self.a
    
    def getSecondToken( self ):
        return self.b
   
    def getContext(self):
        """ Returns a reference to the zope database """
        
        return self.a.namespace["model"]
   
    def toEnglish(self):
        """ Compile this operator into an english sentence """
        raise "NotImplemented", self.__class__.__name__ + "::toEnglish"
    
    def toKERL(self):
        """ 
            Compile this operator into a KERL statement. This is used for debugging
            as it esentially allows us to compile KERL->PCode->KERL, and see the structure
            the optimizer has created
        """                        
        if hasattr( self, '_opname' ): 
            return "[%s,'%s',%s]" % ( self.a.toKERL(), self._opname, self.b.toKERL() )
        
        else:
            raise "NotImplemented", self.__class__.__name__ + "::toKERL"
    
    def toSQL(self):
        """ Compile this Operator to an SQL Statement 
            Used for AssessmentFilters
        """
        raise "NotImplemented", self.__class__.__name__ + "::toSQL"
    
    def toJavascript(self):
        """ Compile this Operator into Javascript
            used within the assessment editor
        """
        raise "NotImplemented", self.__class__.__name__ + "::toJavascript"
    
    def optimize(self, assessment=None, section=None):
        """ Optimizes the expression this operator represents 
        
            * Prunes unneeded branches
            * Pre-execute various statements depending on the passed arguments
        """
                            
        #print "Optimize Single - " + self.toKERL() 
                            
                                    
        if assessment: 
            # If optimizing against an assessment, we can precalculate assessment results
            
            if self.a.implements("AssessmentToken"):
                result = self.execute( assessment )
                
#                print "AssessmentToken"
                
                if result is True:
                    return AlwaysOperator()
                elif result is False:
                    return NeverOperator()
                else:
                    raise UnexpectedResult, "Unexpected result - %s" % `result`
                
            elif section and self.a.implements("ComponentToken"):
                                                                
                component = self.a.getObject()
                
                
                
                #print "Optimizing For Component - %s" % component.getIdentity()                
                
                chash = "_sectioncomponents_" + section.getIdentity()
                
                if chash in section.REQUEST:                
                    componentsInSection = section.REQUEST.get(chash)
                else:
                    #componentsInSection = [component.getIdentity() for component in section.getDescendantsByInstanceOf( component.getClassName() )]
                    componentsInSection = section.getDescendants().keys()
                    section.REQUEST.set( chash, componentsInSection )
                                    
                if component.getIdentity() not in componentsInSection:                                
                    
                    #print "%s not in section, culling" % component.getIdentity()
                    #print section.getIdentity()
                    #print componentsInSection
                    result = self.execute( assessment )

                    #print "Optimizing %s -> %s" % ( self.toKERL(), `result` )

                    if result is True:
                        return AlwaysOperator()
                    elif result is False:
                        return NeverOperator()
                    else:
                        raise UnexpectedResult, "Unexpected Result" 
                else:
                    #print "%s in section, leaving" % component.getIdentity()
                    pass
                    
                #print "No Optimizations!" 
                return self
                    
            else:
                                    
                # No optimizations to be made
                #print "No Optimizations"
                return self
                
                
                
        else:
            #print "No Optimizations."
            return self
            
        
            
            
        
class AlwaysOperator(Operator):
    """ Placeholder for an operation that always returns true. Used internally by the optimizer """
    
    def __init__(self, *args,**kwargs):
        pass
    
    def execute( self, assessment ):
        return True                        
            
    def toEnglish( self ):
        return "Always"
    
    def toSQL( self ):
        return "True"
    
    def toJavascript( self ):
        return "true"    
    
    def toKERL(self):
        return "always" # REMOVE ME
    
class NeverOperator(Operator):
    """ Placeholder for an operation that never returns true. Used internally by the optimizer """
    
    def __init__(self, *args,**kwargs):
        pass
    
    def execute( self, assessment ):
        return False
            
    def toEnglish( self ):
        return "Never"
    
    def toSQL( self ):
        return "False"    
    
    def toJavascript( self ):
        return "false"

    def toKERL(self):
        return "never"

class Equals(Operator):
    """ Operator that determins if two tokens are equal """
       
    _opname = "equals"
       
    def execute( self, assessment ):
        
        return self.a.equals( self.b, assessment=assessment )
        
        #return self.a == self.b
    
    def toKERL( self ):
        return "[%s,'equals',%s]" % ( self.a.toKERL(), self.b.toKERL())
     
    def toEnglish( self ):
        return "%s is equal to '%s'" % (self.a.toEnglish(), self.b.toEnglish())
       
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" = " + self.b.toSQL())
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s == %s" % ( left, right )
    
class Empty(Operator):
    """ Operator that determins if two tokens are equal """
       
    _opname = "empty"
       
    def execute( self, assessment ):
        
        return not self.a.getValue( assessment )
        
    
    def toKERL( self ):
        return "[%s,'empty'" % ( self.a.toKERL())
     
    def toEnglish( self ):
        return "%s is empty" % self.a.toEnglish()
       
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" = ''")
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        
        return "%s == ''" % ( left )
            
class NotEmpty(Empty):
    """ Operator that determins if two tokens are equal """
       
    _opname = "notempty"
       
    def execute( self, assessment ):
        
        return self.a.getValue( assessment )
        
    
    def toKERL( self ):
        return "[%s,'notempty'" % ( self.a.toKERL())
     
    def toEnglish( self ):
        return "%s is not empty" % self.a.toEnglish()
       
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" != ''")
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        
        return "%s != ''" % ( left )
                
        
        
class InRange(Operator):
    """ Operator that determins if two tokens are equal """
       
    _opname = "inrange"
       
    def execute( self, assessment ):
        
        value = self.a.getValue(assessment)
        rangeMin = self.b.getObject()[0]
        rangeMax = self.b.getObject()[1]
                
        return (value >= rangeMin and value <= rangeMax) 
            
        
     
    def toEnglish( self ):
        return "%s is between %s" % (self.a.toEnglish(), self.b.toEnglish())
       
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" BETWEEN " + self.b.toSQL())
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        #right = self.b.toJavascript() 
        
        rangeMin = self.b.getRangeMin()
        rangeMax = self.b.getRangeMax()
        
        return "(%(left)s >= %(rangeMin)s && %(left)s <= %(rangeMax)s)" % locals()
        
class NotInRange(InRange):
    """ Operator that determins if two tokens are equal """
       
    _opname = "notinrange"
       
    def execute( self, assessment ):
        
        return not InRange.execute( self, assessment )
            
    def toEnglish( self ):
        return "%s is not between %s" % (self.a.toEnglish(), self.b.toEnglish())
       
    def toSQL( self ):
        
        return "not (" + InRange.toSQL( self ) + ")"
    
    def toJavascript( self ):
        
        return "!(" + InRange.toJavascript( self ) + ")"
        
        
                
        
class NotEquals(Operator):
    """ Operator that determins if two items are not equal """
    
    _opname = "notequals"
    
    def execute( self, assessment ):
        
        return not self.a.equals( self.b, assessment=assessment )
           
   
    def toKERL( self ):
        return "[%s,'notequals',%s]" % ( self.a.toKERL(), self.b.toKERL())
    
    def toEnglish( self ):
        return "%s is not equal to '%s'" % (self.a.toEnglish(), self.b.toEnglish())
    
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" != " + self.b.toSQL())
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s != %s" % ( left, right )
        
                
        
class Contains(Equals):
    """ Operator that determins if one token contains another (As a substring) """
    
    _opname = "contains"
    
    def execute( self, assessment ):
        
        return self.a.contains( self.b, assessment=assessment )
   
    def toEnglish( self ):
        return "%s contains '%s'" % (self.a.toEnglish(), self.b.toEnglish())
    
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" LIKE '%" + self.b.toSQL(quote=None) + "%'")
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s.indexOf( %s ) >= 0" % ( left, right )
    
class DoesntContain(Contains):
    """ Operator that determins if one token contains another (As a substring) """
    
    _opname = "doesntcontain"
    
    def execute( self, assessment ):
        
        return not Contains.execute( self, assessment )
            
    
    
    def toEnglish( self ):
        return "%s doesnt contain '%s'" % (self.a.toEnglish(), self.b.toEnglish())
    
   
    def toSQL( self ):
        
        return "NOT (" + Contins.toSQL( self ) + ")"
    
    def toJavascript( self ):
        
        return "!(" + Contains.toJavascript(self) + ")"
        
        
   
class LessThan(Operator):
       
    _opname = "lessthan"       
       
    def execute( self ):
        return self.a.getObject() < self.b.getObject()
    
    def toEnglish( self ):
        return "%s is less than '%s'" % (self.a.toEnglish(), self.b.toEnglish())
       
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s < %s" % ( left, right )
    
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" < " + self.b.toSQL())
    
class LessThanEqual(Operator):
       
    _opname = "lessthanequal"
       
    def execute( self ):
        return self.a.getObject() <= self.b.getObject()
    
    def toEnglish( self ):
        return "%s is less than or equal to'%s'" % (self.a.toEnglish(), self.b.toEnglish())
       
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s <= %s" % ( left, right )
    
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" <= " + self.b.toSQL())    
        
class GreaterThan(Operator):
       
    _opname = "greaterthan" 
       
    def execute( self ):
        return self.a.getObject() > self.b.getObject()
    
    def toEnglish( self ):
        return "%s is greater than '%s'" % (self.a.toEnglish(), self.b.toEnglish())        
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() 
        right = self.b.toJavascript() 
                
        #if self.b.implements( "NumericToken" ):
            #left = "parseFloat( %s )" % left 
        
        return "%s > %s" % ( left, right )    
   
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" > " + self.b.toSQL())    
    
class GreaterThanEqual(Operator):
       
    _opname = "greaterthanequal"
       
    def execute( self ):
        return self.a.getObject() >= self.b.getObject()
    
    def toEnglish( self ):
        return "%s is greater than or equal to'%s'" % (self.a.toEnglish(), self.b.toEnglish())        
    
    def toJavascript( self ):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value
        right = self.b.toJavascript() 
        
        return "%s >= %s" % ( left, right )    
    
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" >= " + self.b.toSQL())      
    
class AllOperator(Operator): 
      
    _implements = Operator._implements + ("AllOperator",)
    _opname     = "all"
      
    def __init__(self, *args):
        
        #if not args:
            #raise UnexpectedResult, "AllOperator needs arguments!"
                    
        self.args = args
        
    def toEnglish(self):
        
        clauses = [operator.toEnglish() for operator in self.args]
        english = "(" + " AND ".join( clauses ) + ")"
        
        return english
    
    def toKERL( self ):
        
        inner = ",".join([item.toKERL() for item in self.args])
        
        return "['%s', %s]" % ( self._opname, inner )
    
    def toSQL(self):
        
        if not self.args:
            return "1"
        
        #if not self.args:
            #raise UnexpectedResult, "No arguments in ALL Operator"
        
        clauses = [operator.toSQL() for operator in self.args]
        #print clauses
        sql     = "(1 AND " + (" AND ".join( clauses ) + ")")
        
        return sql
            
    
    def execute( self, assessment ):
        
        # Execute each item sequentially, return false if any fail
        for item in self.args:
            if not item.execute( assessment ): 
                return False
            
        return True
        
    def toJavascript(self):
               
        clauses = []
        for item in self.args:            
            result = item.toJavascript()
            if result: clauses.append( result )
                        
        return " && ".join( clauses )
    
    def optimize(self, assessment=None, section=None):
        
        optimized = []
        
        #print "Optimizing " + str(len(self.args))
        
        for item in self.args:
            result = item.optimize(assessment, section)
                      
            if self.implements( "NoneOperator" ):
                if result.__class__ is AlwaysOperator: return NeverOperator()  # Any True in a Never clause makes the whole thing False
                if result.__class__ is NeverOperator: continue                 # Any False in an NOT clause is redundant
            
            elif self.implements( "AnyOperator" ):
                if result.__class__ is AlwaysOperator: return AlwaysOperator() # Any True in an OR clause makes the whole thing True.
                if result.__class__ is NeverOperator: continue                 # Any False in an OR clause is redundant
            
            elif self.implements( "AllOperator" ):
                if result.__class__ is AlwaysOperator: continue # No use adding 'True' to an AND clause
                if result.__class__ is NeverOperator: return NeverOperator() # Any False in an AND clause immediately causes the whole statement to be false
                
            
            if result: optimized.append( result )
                        
        # Optimized sub-clauses, now work out what to do with ourselves
                        
        if len( optimized ) > 1:
            # Wrap the results in an Any|All|None operator (Depending on which subclass is calling this)
            return self.__class__( *optimized )
        
        if len( optimized ) == 1 and (self.implements( "AnyOperator"  ) or self.implements( "AllOperator" )):
            # Only one sub expression, no real need to wrap with Any/All
            return optimized[0]
            
        else:
            # Optimized down to nothing, 
            if self.implements( "AnyOperator"  ): return NeverOperator()  # An empty AnyOperator should evaluate to false
            if self.implements( "AllOperator"  ): return AlwaysOperator() # An empty AllOperator should evaluate to true
            if self.implements( "NoneOperator" ): return NoneOperator()   # An empty NoneOperator should evaluate to true
            
            raise "WTF" # We should never hit here. TODO: Throw a proper exception
                
        
        
class SimpleOperator(Operator):
    """ An operator that can be easily optimized without side-effects depending for different sections, but cannot be compiled to javascript  """
                                
    def optimize(self, assessment, section=None):
        """ Calculate the value and return a True or False placeholder """
        
        #print "Optimizing Simple"
        
        result = self.execute(assessment)
        
        if result is True:
            return AlwaysOperator()
        elif result is False:
            return NeverOperator()
        else:
            raise UnexpectedResult, result
        

        
class AnyOperator(AllOperator):

    _implements = Operator._implements + ("AnyOperator",)  # Intentionally not declaring implementation of AllOperator 
                                                        # (We subclass it to inherit the optimize() method, but don't want to be considered an implementation of it.
                                                        # FIXME: Should push optimize() to abstract base class.
    _opname     = "any"                                                    

    #def toKERL( self ):
        
        #inner = "],[".join([item.toKERL() for item in self.args])
        
        #return "['any', [" + inner + "]"

    def toEnglish(self):
        
        clauses = [operator.toEnglish() for operator in self.args]
        english = "(" + " OR ".join( clauses ) + ")"
        
        return english
    
    def toSQL(self):
        
        if not self.args:
            return "0"
        
        clauses = [operator.toSQL() for operator in self.args]
        sql     = "(0 OR " + " OR ".join( clauses ) + ")"
        
        return sql    
    
    def execute(self, assessment):
        
        for item in self.args:
            if item.execute(assessment):
                return True
            
        return False
        
    def toJavascript(self):
        
        clauses = []
        for item in self.args:            
            result = item.toJavascript()
            if result: clauses.append( result )
                        
        return " || ".join( clauses )     
        
class NoneOperator(AnyOperator): 
        
    _implements = Operator._implements + ("NoneOperator",)        
         
    _opname     = "none"
         
    def toEnglish(self):
        
        clauses = [operator.toEnglish() for operator in self.args]
        english = "None Of ( " + " OR ".join( clauses ) + " )"
        
        return english
        
    def execute( self, assessment ):
            
        for item in self.args:
            if item.execute( assessment ):
                return False 
            
        return True
        
    def toSQL(self):
        
        if not self.args:
            return "1"
        
        clauses = [operator.toSQL() for operator in self.args]
        sql     = "NOT (0 OR " + " OR ".join( clauses ) + ")"
        
        return sql        
        
    def toJavascript(self):
                 
        # Just negate the Any operator
        return "!( %s )" % AnyOperator.toJavascript(self)
                
                
class SelectedOperator(Operator):
    """ Checkbox/Options Selected """
                    
    _opname = "selected" 
                    
    def __init__(self, a, b):
        
        #print "SelectedOperator( %s, %s )" % ( a, b )
        
        if not b: # If right-hand value is chosen, assume checking boolean component is selected            
            from Products.KnowledgeEngine.KERL.Tokens.BooleanToken import BooleanToken
            b = BooleanToken(True)
            
        Operator.__init__(self, a, b)
                    
    def toKERL(self):
        if self.b.implements( "BooleanToken" ):        
            return "[%s,'selected']" % self.a.toKERL()
        else:
            return "[%s,'selected',%s]" % ( self.a.toKERL(), self.b.toKERL() )
                    
    def toEnglish(self):
        
        return self.a.toEnglish() + " is selected"
    
    #def toSQL(self):
        #raise "NotImplemented", self.__class__.__name__ + "::toSQL"
    
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" = " + self.b.toSQL())
    
    def toJavascript(self):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value        
        right = self.b.toJavascript()
        
        return "%s == %s" % (left, right)
    
    def execute(self, assessment):
        return self.a.equals( self.b, assessment=assessment ) # FIXME : Does this work correctly?
    
                    
                    
class UnselectedOperator(SelectedOperator):
                     
    _opname = "unselected"                     
                     
    def __init__(self, a, b):
        
        #print "SelectedOperator( %s, %s )" % ( a, b )
        
        if not b:
            from Products.KnowledgeEngine.KERL.Tokens.BooleanToken import BooleanToken
            b = BooleanToken( True, {} )
            
        Operator.__init__(self, a, b)
                    
    def toKERL(self):
        return "[%s,'unselected']" % self.a.toKERL()
                    
    def toEnglish(self):
        
        return self.a.toEnglish() + " is unselected"
    
    #def toSQL(self):
        #raise "NotImplemented", self.__class__.__name__ + "::toSQL"
        
    def execute(self, assessment):
        return not self.a.equals( self.b, assessment=assessment ) # FIXME : Does this work correctly?
   
    def toSQL( self ):
        
        template = self.a.toSQL()
         
        return template % (" != " + self.b.toSQL())    
        
    
    def toJavascript(self):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value        
        right = self.b.toJavascript()
        
        return "%s != %s" % (left, right)      
                   
                   
class UndefinedOperator(Operator):
                     
    _opname = "undefined"
                     
    _implements = Operator._implements + ("UndefinedOperator",)   
                    
    def toKERL(self):
        return "[%s,'undefined']" % self.a.toKERL()
                    
    def toEnglish(self):
        
        return self.a.toEnglish() + " is not attempted"
        
    #def execute(self, assessment):
        #return not self.a.equals( self.b, assessment=assessment ) # FIXME : Does this work correctly?
   
    def toSQL( self ):
        sql = """
assessments.assessmentIdentity NOT IN (
    SELECT assessmentIdentity 
    FROM latestAnswers 
    WHERE componentIdentity = %s 
)
"""
        
    
    def toJavascript(self):
        
        left  = self.a.toJavascript() # i.e. document.getElementById(identity).value        
        right = self.b.toJavascript()
        
        return "%s != %s" % (left, right)      
                           
                   
                   
class IsVisible(Operator):
    """ Used for Lazy dependancy evaluation to optimize compiled javascript - Determines if a dependancy is visible """
                       
    _opname = "isvisible"
                       
    _implements = Operator._implements + ("IsVisible",)
                       
    def __init__(self, a):
        
        self.a = a
        
    def toJavascript(self):
                
        identity = self.a.getObject().getIdentity()
        
        return "document.getElementById( 'span%s' ) && document.getElementById( 'span%s' ).style.display != 'none'" % ( identity, identity )
        
    def toKERL(self):
               
        return "[%s,'isvisible']" % self.a.toKERL()
        
    def optimize(self, assessment, section):
        
        return self
        
   
class FirstAttempt(SimpleOperator):
    """ Is this the first attempt for this component? """

    _opname = "firstattempt"

    def execute( self, assessment ):
        
        component = self.a.getObject()
        
        assessmentStorageService = assessment.getModel().getAssessmentStorageService()  
        state = assessmentStorageService.loadComponentState( assessment, component.getIdentity() )
        
        return ( state is None )
            